package carcassonne.lobby;

import carcassonne.game.Game;
import carcassonne.game.GameSetupInfo;
import carcassonne.game.mini.MiniGame;
import carcassonne.game.player.Player;
import carcassonne.game.player.PlayerInfo;
import carcassonne.game.player.PlayerRole;
import carcassonne.game.player.PlayerStatus;
import carcassonne.game.round.RoundManager;
import carcassonne.server.websocket.message.MessageIn;
import carcassonne.server.websocket.message.MessageOut;
import carcassonne.user.UserInfo;
import carcassonne.user.UserManager;
import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.log4j.Logger;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class Room {

    private static final org.apache.log4j.Logger logger = Logger.getLogger(Room.class);
    private final RoomInfo roomInfo;

    public String RoomID;

    public ChannelId HostChannelID;

    public int MaxPlayerCount;

    public int roomStatus;

    public String name;

    public ChannelGroup channelGroup;

    public final RoomSeat seat;
    public final List<Player> players = Collections.synchronizedList(new ArrayList<>());

    public final Map<ChannelId, Player> ChannelPlayerMapping = new ConcurrentHashMap<>();

    public final List<Channel> Observers = Collections.synchronizedList(new ArrayList<>());

    // 原则上每个玩家同时只能有一个未被确认的action
    protected List<List<String>> unconfirmedActions;

    public GameSetupInfo gameSetupInfo;

    public Game game;

    public RoundManager roundManager;

    public Room(RoomInfo roomInfo, String playerID, Channel channel) {
        this.roomInfo = roomInfo;
        RoomID = roomInfo.id;
        HostChannelID = channel.id();
        roomStatus = roomInfo.status;

        MaxPlayerCount = roomInfo.max_player_count;
        if (MaxPlayerCount < 2) MaxPlayerCount = 2;
        if (MaxPlayerCount > 6) MaxPlayerCount = 6;

        seat = new RoomSeat(MaxPlayerCount);

        unconfirmedActions = Collections.synchronizedList(new ArrayList<>(MaxPlayerCount));
        for (int i = 0; i < MaxPlayerCount; i++) {
            unconfirmedActions.add(new ArrayList<>(MaxPlayerCount - 1));
        }

        channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

        AddPlayer(playerID, channel, PlayerRole.Competitor);

        gameSetupInfo = new GameSetupInfo();

//        roundManager = new RoundManager(players);
    }

    public int GetPlayerCount() {
        return players.size();
    }

    public void AddPlayer(String playerID, Channel channel, int role) {
        // 查看用户登录状态
        UserInfo userInfo = UserManager.GetInstance().GetUserInfoWithUserID(playerID);
        if (userInfo == null) {
            channel.close();
            return;
        }

        int index = FindSpace();
        if (index == -1) {
            logger.info("room " + RoomID + " is full!");
            AddObserver(userInfo);
            return;
        }

        channelGroup.add(channel);

        Player player = new Player(userInfo, index, channel, role);
        seat.AddPlayer(player);
        players.add(player);
        ChannelPlayerMapping.put(channel.id(), player);

        NotifyAll("/lobby/add_competitor", player.GetPlayerInfo());
    }

    public void AddObserver(UserInfo userInfo) {

    }

    public Player FindPlayer(String id) {
        for (Player player : players) {
            if (player.id.equals(id)) return player;
        }
        return null;
    }

    public Player FindPlayer(Channel channel) {
        if (!ChannelPlayerMapping.containsKey(channel.id())) return null;
        return ChannelPlayerMapping.get(channel.id());
    }

    public boolean CheckPlayer(Channel channel) {
        return ChannelPlayerMapping.containsKey(channel.id());
    }

    public void RemovePlayer(ChannelId channelID) {
        // 房主退就全退了
        if (HostChannelID.equals(channelID)) {
            Close(channelID);
            return;
        }

        Player player = null;
        for (Player p : players) {
            if (p.channel.id().equals(channelID)) {
                player = p;
                break;
            }
        }
        if (player == null) {
            return;
        }

        seat.RemovePlayer(player);
        players.remove(player);

        ChannelPlayerMapping.remove(channelID);

        RoomManager.GetInstance().Remove(channelID);

        NotifyAll("/lobby/remove_player", player.id);
    }

    public Player GetPlayer(String playerID) {
        for (Player player : players) {
            if (player.id.equals(playerID)) return player;
        }
        return null;
    }

    public void SortPlayers() {
        players.sort(new Comparator<Player>() {
            @Override
            public int compare(Player player1, Player player2) {
                if (player1.seat == player2.seat) return 0;
                return player1.seat > player2.seat ? 1 : -1;
            }
        });
    }

    public List<PlayerInfo> GetPlayerInfos() {
        return seat.GetPlayerInfos();
    }

    // 找一个空位置
    public int FindSpace() {
        return seat.FindSpace();
    }

    public void PlayerSitDown(Channel channel, String id, int from, int to) {
        Player player = FindPlayer(id);
        if (player == null) {
            logger.warn("no player with id " + id);
            return;
        }
        if (!seat.Match(player, from)) {
            logger.warn("sit down player is not match " + from);
            return;
        }
        if (seat.isFull(to)) {
            logger.warn("there is already a player in position " + to);
            return;
        }
        player.SetSeat(to);
        seat.AddPlayer(player);

        NotifyAll("/lobby/update_competitor", player.GetPlayerInfo());
    }

    public Player SetPlayerColor(int index, int color) {
        for (Player player : players) {
            if (player.color == color) return null;
        }

        Player player = seat.GetPlayer(index);
        if (player == null) return null;
        player.SetColor(color);
        return player;
    }

    public int GetAvailableColor() {
        return 0;
    }

    public boolean IsFull() {
        return channelGroup.size() >= MaxPlayerCount;
    }

    public void NotifyHost(String message) {
        channelGroup.find(HostChannelID).writeAndFlush(new TextWebSocketFrame(message));
    }

    public void NotifyExcept(String message) {
        NotifyExcept(HostChannelID, message);
    }

    public void NotifyExcept(Channel channel, String message) {
        NotifyExcept(channel.id(), message);
    }

    public void NotifyExcept(Channel channel, String path, Object data) {
        NotifyExcept(channel.id(), MessageOut.Service(path, data).ToJsonString());
    }

    private final AtomicInteger seqInfo = new AtomicInteger(0);
    private final AtomicInteger chatInfo = new AtomicInteger(0);

    public void Notify(Channel channel, String path, Object data) {
        Notify(channel, MessageOut.Service(path, data).ToJsonString());
    }

    public void Notify(Channel channel, String raw) {
        channel.writeAndFlush(new TextWebSocketFrame(raw));
    }

    public void NotifyExcept(ChannelId channelID, String message) {
        for (Channel channel : channelGroup) {
            if (channel.id().equals(channelID)) continue;
            channel.writeAndFlush(new TextWebSocketFrame(message));
        }
    }

    public void NotifyAll(String path, Object data) {
        MessageOut message = new MessageOut(seqInfo.getAndIncrement(), path, data);
        channelGroup.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
    }

    // 缓存所有的action
    protected List<MessageIn> Actions = new ArrayList<>();

    public void OnReceiveAction(Channel channel, MessageIn message, String raw) {

        int index = message.id;
        if (index >= unconfirmedActions.size()) {
            return;
        }

        List<String> list = unconfirmedActions.get(index);
        unconfirmedActions.set(index, list);

        NotifyExcept(channel, raw);
    }

    public void ConfirmAction(Channel channel, MessageIn message) {

        int index = message.id;
        if (index >= unconfirmedActions.size()) {
            return;
        }

        List<String> list = unconfirmedActions.get(index);
        if (list.contains(channel.id().asLongText())) {
            // 同一个玩家不能重复确认
            return;
        }

        if (list.size() == GetPlayerCount() - 2) {
            Channel c = players.get(index).channel;
            // action被确认，清空列表
            list.clear();
            c.writeAndFlush(new TextWebSocketFrame(MessageOut.OK(message.seq).ToJsonString()));
        } else {
            list.add(channel.id().asLongText());
        }
    }

    public void Close(ChannelId channelID) {
        RoomManager.GetInstance().Remove(channelID);
        RoomManager.GetInstance().Remove(channelGroup, this);
        channelGroup.close();
    }

    public RoomInfo toRoomInfo() {
        roomInfo.player_count = channelGroup.size();
        return roomInfo;
    }

    public void StartGame(GameSetupInfo gameSettings) {
        this.gameSetupInfo = gameSettings;
        roomStatus = RoomStatus.InGame;

        SortPlayers();
    }

    public void EndGame() {
        players.clear();
        ChannelPlayerMapping.clear();
    }

    public void Reset() {
        seat.Clear();
        players.clear();
    }

    public void LoadGameReady(Channel channel) {
        Player player = ChannelPlayerMapping.get(channel.id());
        if (player == null) {
            return;
        }
        NotifyExcept(channel, "/game/load_game_ready", player.sequence);
        player.status = PlayerStatus.InGame;

        // 如果所有玩家都加载完成，则游戏开始
        for (Player p : players) {
            if (p.status != PlayerStatus.InGame) return;
        }
        logger.info("------------ all players load game ready, game started! ------------");
        NotifyAll("/game/start_game", null);
    }

    public final Map<String, Object> miniGames = new ConcurrentHashMap<>();

    public <T extends MiniGame> T GetMiniGame(String id, Class<T> clazz) {
        if (miniGames.containsKey(id)) {
            return clazz.cast(miniGames.get(id));
        }
        try {
            T instance = clazz.getDeclaredConstructor().newInstance();
            instance.Init(players);
            miniGames.put(id, instance);
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    protected Map<Integer, HashSet<Channel>> readyMap = new HashMap<>();

    // 返回所有玩家是否都就绪，此方法应该是幂等的
    public boolean PlayerReady(Channel channel, int key) {
        if (!readyMap.containsKey(key)) {
            readyMap.put(key, new HashSet<>());
        }
        HashSet<Channel> readyPlayers = readyMap.get(key);
        readyPlayers.add(channel);
        if (readyPlayers.size() == players.size()) {
            readyMap.remove(key);
            return true;
        }
        return false;
    }
}
